The Big Idea
This article is for educational purposes only. Note the license agreement (EULA) states that modification of the software is strictly
prohibited, however, reverse engineering rights are protected by the French copyright laws. I am not to be held accountable for any
misfortunes this article brings you.
If you haven’t already, read Part 1 first. Here, we’ll
create a patch based on the methods explained in Part 1. On a more technical side, this patcher will attempt to find certain signatures present
around the critical points of code we want to modify. The tricky thing’s that we must find the right jump instructions and targets before
overwriting them.
Steps to Success
Getting it to work first
Let’s use Python to prototype. I’ve set up a virtualenv for Python 2.7 and installed pwntools. This will allow us to rapidly prototype and test various signatures in the file. Fortunately, pwntools is well documented unlike some other libraries we’ll encounter soon . Let’s begin by writing the python script:
1 |
|
Recall that a stack trace eventually lead us to a function call 0x638031
, where several instructions above
was the important test al, al
and jz
pattern:
1 |
|
Looking at other portions of the function, we see a peculiar call near the end:
1 |
|
Note the timer object. What’s even more interesting is that the value 0x1B7740
stored into esi
is 1800000 in decimal.
Where 1800000 milliseconds is 30 minutes… Wait… The free version of Hopper has a 30-minute session limit and we just found when
the timer is initiated! This means that we can reliably use the start timer code as a signature and a way to locate a call to CheckLicense
.
Moreover, the call to CheckLicense
several instructions prior is conveniently located as we can filter all matches by proximity to the timer
code. The specific signature that is guaranteed to be unique and present would be the mov esi, 1B7740h
instruction as it’s logical to
assume that no other parts of the program contain it. The encoded instruction is as follows:
1 |
|
We can then search the entire ELF file like so:
1 |
|
Then we must locate a call to CheckLicense
and obtain the address to the symbol. We can do this by searching all test al, al
and see if a
call
has been made right before. We then take the call
closest to our sig1
to maximize our chances of finding the correct symbol.
We will be using this technique over and over again, locating all the critical signatures in the file. Note that there is a chance that the
signature targets the wrong piece of code - that’s just a tradeoff of simplicity over accuracy. Let’s take a look at the assembly
instructions versus the hex encoded version:
1 |
|
What we can do is find all addresses (we’ll call this addr
) of 84 C0
patterns in .text
and check if addr-5
is equal to E8
(opcode for
a call
). Then extract the following 4 bytes which should be a rel32
address (4-byte relative address). The opcode encoding for a call is as
follows (table from coder64’s x86 Opcode and Instruction Reference):
po | st | mnemonic | op1 op2 op3 op4 | description, notes |
---|---|---|---|---|
E8 |
D32 |
CALL |
rel16/32 |
Call Procedure |
Now for the python code:
1 |
|
Great! We now have the location of CheckLicense
stored in adr1
!
In this case, we use pwntools
own builtin disassembler to extract the call location. However, when we write the final C++ patcher, it’s
easier to just interpret the raw hex data directly, hence the importance of the instruction reference. We can now patch it like so:
1 |
|
What’s left to do now is to locate the print license dialog and overwrite the jump instructions. The first step is to search for the string
Personal License\nRegistered to %1\n%2
and locate all XREFs to there. However, the only XREF to the string is in the form of a lea
instruction. In fact, the lea
instruction only takes an offset which we will have to manually calculate:
1 |
|
Implemented in python:
1 |
|
However, we’re not done as we need to locate the correct test eax, 1
instruction (it’s more distinct than a test eax, eax
):
1 |
|
Using this, we can distinctly locate the correct test eax, eax
instruction:
1 |
|
And completing our patch:
1 |
|
Full Python PoC Script
1 |
|
Improved C++ Version
Python is great for PoC scripts, not so much for deployment. C++, on the other hand, can link all your dependencies statically and deployment should go smoothly. However…
The Pain, Self-Harm and Horribly Documented Library
I’ve decided to use the LIEF library, as that was the easiest way to modify and manipulate the ELF format. Setting it up is as easy as copying the example CMake configuration and renaming the files to suit your needs. We can start by loading the ELF file:
1 |
|
We can then search for our signatures in a similar mannar to python:
1 |
|
However, the processing is slightly more challenging:
1 |
|
To understand what’s going on, we need to again return to the raw hex encoded instructions:
1 |
|
When we search for references, the resultant addr
really references the 84
byte. That means that addr-5
references the E8
byte, which
is the opcode for a call
instruction. The following 4 bytes after E8
is an offset from the current position. Note that Intel x86 is
in little-endian, which means 32 C5 EC FF
really encodes for the integer FFECC532
. So we obtain the beginning of the offset at
addr-4
and decode the sequence to be an integer. We then add it back to our addr
to obtain the final destination of the call
.
The code for vec2int32
is quite trivial:
1 |
|
As is porting the rest of our python script. Note that before, we forgot to patch and overwrite the annoying Demo Version
watermark.
We’ll do that here:
1 |
|
This also shows how to patch the binary given the virtual address. One headache we’ll encounter is encoding the instruction for:
1 |
|
Notice how we avoided using a disassembler/assembler by decoding the instructions ourselves. Here, we’ll encode this ourselves with
extra nop
s near the end (as padding) for good luck.
1 |
|
With that, we can patch like so:
1 |
|
Results (for real now)
To avoid spoonfeeding you too much code, try porting the python version over to C++. It should be really trivial by now as I already clarified and resolved many of the major hurdles.
If your browser only supports downloading of the Base64 encoded file, run this to convert it back to a binary:
1 |
|
If you’re on a Mac, I’m sorry ¯\_(ツ)_/¯
To run the patcher, simply execute:
1 |
|
What’s Next?
In part 3, we’ll attempt to explore more ways to patch Hopper! Until then, PEACE OUT.